﻿/******************************************************************************\
    Auxiliary.cpp contains plugin-depended functions.
    It's used with acm server plugins. See DllMain.cpp for details.
    Copyright (C) 2008  Victor Vinogradov (fly3333@gmail.com)

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
\******************************************************************************/

#include <winsock2.h>
#include <windows.h>
#include ".\Auxiliary.h"
#include <Common\String.h>
#include "..\Post.h"
#include <Common\Log.h>
#include <Common\KMP.h>
#include <Common\Random.h>
#include <Common\Memory.hpp>
#include <Common\Plugin.h>
#include <set>
#include <cmath>
using namespace std;

extern cLog Log;

cCriticalSection AboutCriticalSection;
const char Title[] = "РЁРєРѕР»Р° РїСЂРѕРіСЂР°РјРјРёСЃС‚Р° plugin " __DATE__;
const char Comment[] = "<a href='http://acmp.ru/'>РЁРєРѕР»Р° РїСЂРѕРіСЂР°РјРјРёСЃС‚Р°</a>";

cCriticalSection LanguagesCriticalSection;
map< unsigned long, unsigned long > LanguageIndex;
vector< sLanguage > Language;

cCriticalSection ProblemsCriticalSection;
map< unsigned long, unsigned long > ProblemIndex;
vector< sProblem > Problem;

static const char Host[] = "acmp.ru";

cCriticalSection VariablesCriticalSection;
const unsigned long VariablesCount = 2;
const char *VariableProperties[ VariablesCount ] =
{
	"Name = Login;",
	"Name = Password;",
};
string VariableValue[ VariablesCount ];
static string &Login = VariableValue[ 0 ];
static string &Password = VariableValue[ 1 ];

cCriticalSection CompilationErrorCriticalSection;
std::map< unsigned long, std::string > CompilationError;

cCriticalData< unsigned long > MemId( 0 );
cCriticalData< bool > IsLoggedIn( false );

bool LogIn()
{
	static cCriticalSection login_critical_section;
	cLocker locker( login_critical_section );
	try
	{
		if( IsLoggedIn )
			return true;

		string response, location;

		if( !HttpGet( Host, "http://acmp.ru/", response ) )
		{
			Log.Error( "Login::HttpGet returned false.\n" );
			return false;
		}

		int pos = 0, new_pos;
		const char *str = response.c_str();
		int str_length = ( int )response.length();
		
		static const cCIKMPString keystr( "<form class=nomargin name=enter action=/index.asp?main=enter&r=" );
		new_pos = keystr.FindIn( str + pos, str_length - pos );
		if( new_pos == -1 )
		{
			Log.Error( "Login: Key string was not found.\n" );
			return false;
		}
		pos += new_pos + keystr.Length();
		new_pos = strchr( str + pos, ' ' ) - ( str + pos );
		string pass_str( str + pos, new_pos );

		string login, password;
		{
			cLocker locker( VariablesCriticalSection );
			login = Login;
			password = Password;
		}

		if( !HttpPost( Host, StringPrintf( "http://acmp.ru/index.asp?main=enter&r=%s", pass_str.c_str() ), StringPrintf( "lgn=%s&password=%s", login.c_str(), password.c_str() ), response, location, StringPrintf( "http://%s/", Host ).c_str() ) )
		{
			Log.Error( "Login::HttpPost returned false.\n" );
			return false;
		}

		if( !HttpGet( Host, "http://acmp.ru/", response ) )
		{
			Log.Error( "Login::HttpGet returned false.\n" );
			return false;
		}

		pos = 0;
		str = response.c_str();
		str_length = ( int )response.length();

		unsigned long mem_id;
		static const cCIKMPString memstr( "<img src=/images/circle.gif width=10 height=10> <a href=/index.asp?main=user&id=" );
		new_pos = memstr.FindIn( str + pos, str_length - pos );
		if( new_pos == -1 )
		{
			Log.Error( "Login: Key string was not found.\n" );
			return false;
		}
		pos += new_pos + memstr.Length();
		sscanf( str + pos, "%lu", &mem_id );

		MemId = mem_id;

		IsLoggedIn = true;
		return true;
	}
	catch( ... )
	{
		Log.Error( "Login: Exception O.o\n" );
		return false;
	}
}

bool LoadProblems()
{
	cLocker locker( ProblemsCriticalSection );
	try
	{
		static bool is_problems_loaded = false;
		if( is_problems_loaded )
			return true;
		if( !LogIn() )
			return false;

		Problem.clear();
		ProblemIndex.clear();

		string response;

		for( int page_id = 0; ; ++page_id )
		{
			if( !HttpGet( Host, StringPrintf( "http://acmp.ru/index.asp?main=tasks&str=%%20&page=%d&id_type=0", page_id ), response ) )
			{
				Log.Error( "LoadProblems::HttpGet returned false.\n" );
				return false;
			}

			int pos = 0, new_pos;
			const char *str = response.c_str();
			int str_length = ( int )response.length();

			while( true )
			{
				static const cCIKMPString problemstr( "<a href=?main=task&id_task=" );
				new_pos = problemstr.FindIn( str + pos, str_length - pos );
				if( new_pos == -1 )
					break;
				pos += new_pos + problemstr.Length();

				unsigned long id;
				sscanf( str + pos, "%lu%n", &id, &new_pos );
				pos += new_pos;

				new_pos = strchr( str + pos, '>' ) - ( str + pos );
				pos += new_pos + 1;
				new_pos = strchr( str + pos, '<' ) - ( str + pos );
				string header( str + pos, new_pos );
				pos += new_pos + 1;

				header = ConvertCodePage( header, 1251, CP_UTF8 );

				ProblemIndex.insert( make_pair( id, Problem.size() ) );
				Problem.push_back( sProblem( id, header ) );
			}

			if( pos == 0 ) 
				break;
		}

		if( Problem.size() == 0 )
		{
			Log.Error( "LoadProblems: Condition (Problem.size()==0) is true.\n" );
			return false;
		}

		is_problems_loaded = true;
		return true;
	}
	catch( ... )
	{
		Log.Error( "LoadProblems: exception O.o\n" );
		return false;
	}
}

bool sProblem::Load()
{
	cLocker locker( ProblemsCriticalSection );
	try
	{
		if( IsLoaded )
			return true;

		bool first_try = true;
		while( true )
		{
			if( !LogIn() )
				return false;

			if( !HttpGet( Host, StringPrintf( "http://acmp.ru/index.asp?main=task&id_task=%lu", Id ), Body ) )
			{
				Log.Error( "sProblem::Load::HttpGet returned false.\n" );
				return false;
			}

			if( !Cut( Body, "<td colspan=\"3\" background=/images/notepad2.gif style=\"BORDER-BOTTOM: #306E18 1px dashed;  BORDER-left: #306E18 1px dashed;BORDER-right: #306E18 1px dashed; padding: 3\" height=100%>", "<h2>Сдать задачу</h2>" ) )
			{
				Log.Error( "sProblem::Load::Cut returned false.\n" );
				if( first_try )
				{
					first_try = false;
					IsLoggedIn = false;
					continue;
				}
				return false;
			}

			string header = StringPrintf( "<html><head><title>%s</title><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"><link rel=\"icon\" href=\"/images/favicon.ico\" type=\"image/x-icon\"><link rel=\"shortcut icon\" href=\"/images/favicon.ico\" type=\"image/x-icon\"><link rel=\"stylesheet\" type=\"text/css\" href=\"/design/acmp_style.css\"></head><body class=nomargin>", Header.c_str() );
			string tail = "</body></html>";
			Body = header + ConvertCodePage( Body, 1251, CP_UTF8 ) + tail;

			if( !LoadAdditions( Host, "/", Body, AdditionId ) )
			{
				Log.Error( "sProblem::Load::LoadAdditions returned false.\n" );
				return false;
			}

			break;
		}

		IsLoaded = true;
		return true;
	}
	catch( ... )
	{
		Log.Error( "sProblem::Load: exception O.o\n" );
		return false;
	}
}

bool LoadLanguages()
{
	cLocker locker( LanguagesCriticalSection );
	try
	{
		static bool is_languages_loaded = false;
		if( is_languages_loaded )
			return true;

		LanguageIndex.insert( make_pair( 1, Language.size() ) );
		Language.push_back( sLanguage( 1, "Microsoft Visual C++ 7.1", "solution.cpp" ) );

		LanguageIndex.insert( make_pair( 2, Language.size() ) );
		Language.push_back( sLanguage( 2, "Java 2 SDK 1.5 (public class Solution)", "Solution.java" ) );

		LanguageIndex.insert( make_pair( 3, Language.size() ) );
		Language.push_back( sLanguage( 3, "Borland Delphi 7.0", "solution.dpr" ) );

		LanguageIndex.insert( make_pair( 4, Language.size() ) );
		Language.push_back( sLanguage( 4, "Microsoft QBasic 4.5", "solution.bas" ) );

		is_languages_loaded = true;
		return true;
	}
	catch( ... )
	{
		Log.Error( "LoadLanguages: exception O.o\n" );
		return false;
	}
}

static unsigned long LastSubmitId;

bool GetLastSubmit()
{
	try
	{
		static bool is_last_submit_gotten = false;
		if( is_last_submit_gotten )
			return true;

		if( !LogIn() )
			return false;

		string response;
		if( !HttpGet( Host, StringPrintf( "http://acmp.ru/index.asp?main=status&id_mem=%lu", ( unsigned long )MemId ), response ) )
		{
			Log.Error( "GetLastSubmit::HttpGet returned false.\n" );
			return false;
		}

		int pos = 0, new_pos;
		const char *str = response.c_str();
		int str_length = ( int )response.length();

		const cCIKMPString keystr( "<td><a href=?main=source&id=" );
		new_pos = keystr.FindIn( str + pos, str_length - pos );

		LastSubmitId = 0;
		if( new_pos != -1 )
		{
			pos += new_pos + keystr.Length();
			if( sscanf( str + pos, "%lu", &LastSubmitId ) != 1 )
				LastSubmitId = 0;
		}

		Log.Notice( "GetLastSubmit: Last submit id = %lu\n", LastSubmitId );

		is_last_submit_gotten = true;
		return true;
	}
	catch( ... )
	{
		Log.Error( "GetLastSubmit: Exception O.o\n" );
		return false;
	}
}

bool SubSubmit( unsigned long problem_index, unsigned long language_index, const string &source, long &result, unsigned short &test_number, unsigned long &time, unsigned long &memory )
{
	static cCriticalSection submit_critical_section;
	cLocker locker( submit_critical_section );
	try
	{
		if( !GetLastSubmit() )
			return false;

		if( source.size() >= 500 * 1024 )
		{
			result = RESULT_SOURCE_LIMIT_EXCEEDED;
			return true;
		}

		string response, location;

		unsigned long problem_id;
		{
			cLocker locker( ProblemsCriticalSection );
			problem_id = Problem[ problem_index ].Id;
		}

		string language;
		{
			cLocker locker( LanguagesCriticalSection );
			language = Language[ language_index ].FileName;
		}

		bool first_try = true;
		while( true )
		{
			if( !LogIn() )
				return false;

			string boundary = "---------------------------" + RandomString( 14 );
			if( !HttpMultiPost( Host, StringPrintf( "http://acmp.ru/?main=update&mode=upload&id_task=%lu", problem_id ), boundary, StringPrintf( "--%s\r\nContent-Disposition: form-data; name=\"fname\"; filename=\"%s\"\r\nContent-Type: text/plain\r\n\r\n", boundary.c_str(), language.c_str() ) + source + StringPrintf( "\r\n--%s--\r\n", boundary.c_str() ), response, location, StringPrintf( "http://%s/?main=task&id_task=%lu", Host, problem_id ).c_str() ) )
			{
				Log.Error( "SubSubmit::HttpPost returned false.\n" );
				return false;
			}

			// пїЅпїЅ, пїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ? пїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅ пїЅпїЅпїЅ!!!
			//if( location == "Antispam" )
			//{
			//	Write( "SubSubmit: Antispam protection enabled. Waiting 30 sec.\n" );
			//	Sleep( 30000 );
			//	continue;
			//}

			int dummy;
			if( sscanf( location.c_str(), "?main=status&r=%d", &dummy ) != 1 )
			{
				Log.Error( "SubSubmit: Submission was not successful. Wrong location: %s\n", location.c_str() );
				if( first_try )
				{
					first_try = false;
					IsLoggedIn = false;
					continue;
				}
				return false;
			}

			break;
		}

		const int MAX_COUNT = 100;
		int count = 0;

		while( true )
		{
			if( !HttpGet( Host, StringPrintf( "http://acmp.ru/index.asp?main=status&id_mem=%lu", ( unsigned long )MemId ), response ) )
			{
				Log.Error( "SubSubmit::HttpGet returned false.\n" );
				return false;
			}

			int pos = 0, new_pos;
			const char *str = response.c_str();
			int str_length = ( int )response.length();

			const cCIKMPString keystr( "<td><a href=?main=source&id=" );
			new_pos = keystr.FindIn( str + pos, str_length - pos );
			unsigned long submit_id = LastSubmitId;
			if( new_pos != -1 )
			{
				pos += new_pos + keystr.Length();
				if( sscanf( str + pos, "%lu", &submit_id ) != 1 )
					submit_id = LastSubmitId;
			}
			if( submit_id == LastSubmitId )
			{
				++count;
				if( count == MAX_COUNT )
				{
					Log.Error( "SubSubmit: Too many loops.\n" );
					return false;
				}
				Sleep( 100 );
				continue;
			}

			static const cCIKMPString tdstr( "<td>" ), stdstr( "</td>" );
			new_pos = tdstr.FindIn( str + pos, str_length - pos );
			pos += new_pos + tdstr.Length();

			new_pos = tdstr.FindIn( str + pos, str_length - pos );
			pos += new_pos + tdstr.Length();

			new_pos = tdstr.FindIn( str + pos, str_length - pos );
			pos += new_pos + tdstr.Length();

			new_pos = tdstr.FindIn( str + pos, str_length - pos );
			pos += new_pos + tdstr.Length();
			new_pos = stdstr.FindIn( str + pos, str_length - pos );
			string submission_result( str + pos, new_pos );
			pos += new_pos + tdstr.Length();

			new_pos = tdstr.FindIn( str + pos, str_length - pos );
			pos += new_pos + tdstr.Length();
			new_pos = stdstr.FindIn( str + pos, str_length - pos );
			if( sscanf( str + pos, "%hu", &test_number ) != 1 )
				test_number = 0;
			pos += new_pos + tdstr.Length();

			new_pos = tdstr.FindIn( str + pos, str_length - pos );
			pos += new_pos + tdstr.Length();
			new_pos = stdstr.FindIn( str + pos, str_length - pos );
			double runtime;
			if( sscanf( Concat( Split( string( str + pos, new_pos ), "," ), "." ).c_str(), "%lf", &runtime ) == 1 )
				time = ( int )floor( runtime * 1000 + 0.5 );
			else
				time = 0;
			pos += new_pos + tdstr.Length();

			new_pos = tdstr.FindIn( str + pos, str_length - pos );
			pos += new_pos + tdstr.Length();
			new_pos = stdstr.FindIn( str + pos, str_length - pos );
			int size;
			char type[ 4 ];
			if( sscanf( string( str + pos, new_pos ).c_str(), "%d%s", &size, type ) != 2 )
				memory = 0;
			else if( strcmp( type, "Кб" ) == 0 )
				memory = size * 1024;
			else if( strcmp( type, "Мб" ) == 0 )
				memory = size * 1024 * 1024;
			else 
				memory = 0;
			pos += new_pos + tdstr.Length();

			if( submission_result == "<span class=black>Waiting</span>" ||
				submission_result == "<span class=black>Running</span>" ||
				submission_result == "<span class=black>Compiling</span>" )
			{
				++count;
				if( count == MAX_COUNT )
				{
					Log.Error( "SubSubmit: Too many loops.\n" );
					return false;
				}
				Sleep( 100 );
				continue;
			}

			LastSubmitId = submit_id;
			
			if( submission_result == "<span class=green>Accepted</span>" )
				result = RESULT_ACCEPTED;
			else if( submission_result == "<span class=red>Compilation error</span>" )
			{
				if( !HttpGet( Host, StringPrintf( "http://acmp.ru/index.asp?main=source&id=%lu", submit_id ), response ) )
				{
					Log.Error( "SubSubmit::HttpGet returned false.\n" );
					return false;
				}

				result = RESULT_COMPILATION_ERROR;

				static const cCIKMPString beginstr( "<font size=2 face=courier color=red>" );
				static const cCIKMPString endstr( "</font>" );

				int pos = 0, new_pos;
				const char *str = response.c_str();
				int str_length = ( int )response.length();

				new_pos = beginstr.FindIn( str + pos, str_length - pos );
				pos += new_pos + beginstr.Length();
				new_pos = endstr.FindIn( str + pos, str_length - pos );
				string error( str + pos, new_pos );
				pos += new_pos + endstr.Length();

				error = Concat( Split( error, "&nbsp;" ), " " );
				error = Concat( Split( error, "<br>" ), "\n" );
				{
					cLocker locker( CompilationErrorCriticalSection );
					CompilationError[ GetCurrentThreadId() ] = error;
				}
			}
			else if( submission_result == "<span class=red>Wrong answer</span>" )
				result = RESULT_WRONG_ANSWER;
			else if( submission_result == "<span class=red>Time limit exceeded</span>" )
				result = RESULT_TIME_LIMIT_EXCEEDED;
			else if( submission_result == "<span class=red>Runtime error</span>" )
				result = RESULT_RUN_TIME_ERROR;
			else if( submission_result == "<span class=red>Memory limit exceeded</span>" )
				result = RESULT_MEMORY_LIMIT_EXCEEDED;
			else if( submission_result == "<span class=red>Presentation error</span>" )
				result = RESULT_PRESENTATION_ERROR;
			else
			{			
				Log.Error( "SubSubmit: Unknown result \"%s\"\n", submission_result.c_str() );
				return false;
			}
			break;
		}         

		return true;
	}
	catch( ... )
	{
		Log.Error( "SubSubmit: exception O.o\n" );
		return false;
	}
}
